#include <memory.h>
#include <malloc.h>
#include <errno.h>
#include <util.h>
#include <arch/cache.h>
#include <arch/mmu.h>

LIST_HEAD(memory_banks);
LIST_HEAD(memory_zones);
LIST_HEAD(memory_dma_zones);
LIST_HEAD(memory_sram_zones);
LIST_HEAD(memory_sdram_zones);

int memory_bank_add(struct memory_bank *bank, unsigned int count)
{
	struct list_head *p;
	struct memory_bank *b;

	if (!bank || IS_ERR(bank) || !count)
		return -EINVAL;

	if (!bank->length || (bank->start_addr & (SZ_1K - 1)) || (bank->length & (SZ_1K - 1)))
		return -EINVAL;

	if (bank->type >= MT_NR)
		return -EINVAL;

	for_each_memory_bank(p) {
		b = container_of(p, struct memory_bank, n_bank_list);
		if (((bank->start_addr >= b->start_addr) && (b->start_addr + b->length > bank->start_addr))
				|| ((b->start_addr >= bank->start_addr) && (bank->start_addr + bank->length > b->start_addr))) 
			return -EINVAL;
	}
	
	for_each_memory_bank(p) {
		b = container_of(p, struct memory_bank, n_bank_list);
		if (bank->start_addr < b->start_addr) {
			list_add_tail(&bank->n_bank_list, p);	
			return 0;
		}
	}
	list_add_tail(&bank->n_bank_list, &memory_banks);	
	return 0;
}

int memory_zone_add(struct memory_zone *zone, unsigned int count)
{
	struct list_head *p;
	struct memory_zone *z;
	struct memory_bank *b;

	if (!zone || IS_ERR(zone) || !count)
		return -EINVAL;

	if (!zone->length || (zone->start_addr & (SZ_16 - 1)) || (zone->length & (SZ_16 - 1)))
		return -EINVAL;

	if (zone->type >= MZ_NR)
		return -EINVAL;

	if (!zone->allocator)
		zone->allocator = default_memory_allocator;

	for_each_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_list);
		if (((zone->start_addr >= z->start_addr) && (z->start_addr + z->length > zone->start_addr))	
				|| ((z->start_addr >= zone->start_addr) && (zone->start_addr + zone->length > z->start_addr)))
			return -EINVAL;
	}

	for_each_memory_bank(p) {
		b = container_of(p, struct memory_bank, n_bank_list);
		if ((b->start_addr <= zone->start_addr) && (b->start_addr + b->length >= zone->start_addr + zone->length)) {
			zone->alloc_end = zone->start_addr;
			zone->bank = b;
			zone->priv = NULL;
			
			for_each_memory_zone(p) {
				z = container_of(p, struct memory_zone, n_zone_list);
				if (z->length < zone->length) {
					list_add_tail(&zone->n_zone_list, p);
					break;
				}
			}			
			if (p == &memory_zones) 
				list_add_tail(&zone->n_zone_list, &memory_zones);
				
			if (zone->type == MZ_DMA) {
				for_each_dma_memory_zone(p) {
					z = container_of(p, struct memory_zone, n_zone_type_list);
					if (z->length > zone->length) {
						list_add_tail(&zone->n_zone_type_list, p);
						break;
					}
				}			
				if (p == &memory_dma_zones)
					list_add_tail(&zone->n_zone_type_list, &memory_dma_zones);	
			} else if (b->type == MT_SRAM) {
				for_each_sram_memory_zone(p) {
					z = container_of(p, struct memory_zone, n_zone_type_list);
					if (z->length > zone->length) {
						list_add_tail(&zone->n_zone_type_list, p);
						break;
					}
				}
				if (p == &memory_sram_zones)
					list_add_tail(&zone->n_zone_type_list, &memory_sram_zones);	
			} else if (b->type == MT_SDRAM) {
				for_each_sdram_memory_zone(p) {
					z = container_of(p, struct memory_zone, n_zone_type_list);
					if (z->length > zone->length) {
						list_add_tail(&zone->n_zone_type_list, p);
						break;
					}
				}
				if (p == &memory_sdram_zones)			
					list_add_tail(&zone->n_zone_type_list, &memory_sdram_zones);	
			}
			return 0;
		}
	}

	return -EINVAL;
}

int memory_init(void)    
{
    struct list_head *p;
    struct memory_zone *z;
	int ret;

    // icache must be enabled, dcache and mmu must be disabled
	if (!icache_is_enabled())
		icache_enable();
	if (dcache_is_enabled())
		dcache_disable();
	if (mmu_is_enabled())
		mmu_disable();
    ret = memory_map_init();   
	if (IS_ERR(ret))
		return ret;
    dcache_invalidate_all();  
    dcache_enable();     

    for_each_memory_zone(p) { 
        z = container_of(p, struct memory_zone, n_zone_list);
		if (z->allocator) {
			ret = z->allocator->startup(z); 
			if (IS_ERR(ret))
				return ret;
		}
    }

	return ret;
}

